/**
 * @class 通知发送器
 * @author DuskyHuang 圣子
 * @description 用于管理全局通知的监听和发送，支持优先级排序
*/
export class Notifier {

    public constructor(
        public name = "",
        private _map: Map<any, IRecord[]> = new Map
    ) { }

    /**
     * 添加通知的监听
     * @param type 通知类型
     * @param handler 回调方法
     * @param observer 回调上下文
     * @param once 是否执行一次后删除，默认否
     * @param priority 优先级，越大越先调用，默认=0
     * @return 是否成功添加
     * @explain 当重复添加相同回调方法和上下文的监听时，添加失败
    */
    public attach(type: string | number, handler: Handler, observer?: unknown, once: boolean = false, priority: number = 0): bool.res {
        if (isEmpty(type)) console.warn('attach empty type');
        priority = isNum(priority) ? priority : 0;
        if (!Array.isArray(this._map.get(type))) this._map.set(type, []);
        const recs = this._map.get(type);
        for (let rec of recs) {
            const { handler: h, observer: o } = rec;
            if (handler === h && !!observer && observer === o) {
                console.warn('duplicate attached');
                return false;
            }
        }
        const idx = recs.findIndex(rec => rec.priority < priority);
        const rec = { handler, observer, priority, once };
        idx < 0 ? recs.push(rec) : recs.splice(idx, 0, rec);
        return true;
    }

    /**
     * 移除通知监听
     * @param type 通知类型
     * @param handler 回调方法
     * @param observer 回调上下文
     * @return 移除条数（命中条数）
     * @explain 当指定回调上下文时，只有与该上下文相同的一条通知监听会被移除，否则全部移除
     */
    public detach(type: string | number, handler: Handler, observer?: any): num.int {
        let recs = this._map.get(type), removed = 0;
        if (notEmptyArr(recs)) {
            for (let i = 0; i < recs.length; i++) {
                const { handler: h, observer: o } = recs[i];
                const bingo2 = !observer && handler === h;
                const bingo1 = !!observer && observer === o && handler === h;
                if (bingo1 || bingo2) {
                    recs.splice(i, 1);
                    i--;
                    removed++;
                }
            }
        }
        return removed;
    }

    /**
     * 移除某上下文相关的所有监听通知
     * @param observer 回调上下文
     * @return 移除条数（命中条数）
     */
    public unload(observer: any): num.int {
        let removed = 0;
        if (!!observer) {
            const values = Array.from(this._map.values());
            for (let recs of values) {
                for (let i = 0; i < recs.length; i++) {
                    const { observer: o } = recs[i];
                    if (observer === o) {
                        recs.splice(i, 1);
                        i--;
                        removed++
                    }
                }
            }
        }
        return removed;
    }

    /**
     * 发送通知
     * @param type 通知类型
     * @params args 参数罗列
     * @return 移除条数（命中条数）
     */
    public dispatch(type: string | number, ...args: any[]): num.int {
        const records = this._map.get(type);
        if (notEmptyArr(records)) {
            let { length } = records;
            for (let i = 0; i < length; i++) {
                const { observer, handler, once } = records[i];
                try {
                    Function.invoke(handler, observer, ...args);
                } catch (e) {
                    console.error(e);
                }
                if (once) {
                    records.splice(i, 1);
                    i--;
                }
            }
            return length;
        } else return 0;
    }

    /**
     * 是否注册了某类型的通知
     * @param type 通知类型
     * @return 判断结果
     */
    public hasType(type: string | number): bool.res {
        return notEmptyArr(this._map.get(type));
    }

    /**
     * 是否注册了某监听回调
     * @param handler 回调方法
     * @param observer 回调上下文
     * @return 判断结果
     * @explain 当指定回调上下文时，只有与该上下文相同的通知才被视为相同。
     */
    public hasHandler(handler: Handler, observer?: any): bool.res {
        const values = this._map.values();
        for (let recs of values) {
            for (let rec of recs) {
                const { handler: h, observer: o } = rec;
                if (!o && handler === h) return true;
                if (!!o && observer === o && handler === h) return true;
            }
        }
        return false;
    }

    /**
     * 清空某类通知
     * @param type 需要移除的通知类型
     * @return 是否有通知被移除
     */
    public clear(type: string | number): bool.exist {
        const exist = this._map.has(type);
        if (exist) this._map.delete(type);
        return exist;
    }

    /**
     * 清空所有通知
     */
    public clearAll(): void {
        this._map.clear();
    }
}

interface IRecord {
    handler: Handler;
    priority: number;
    observer?: any;
    once?: boolean;
}